package com.chalmers.irc2you.network;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringReader;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.LinkedList;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.provider.Contacts.Settings;
import android.util.Log;
import android.widget.Toast;

import com.chalmers.irc2you.model.Channel;
import com.chalmers.irc2you.model.Message;
import com.chalmers.irc2you.model.Sha1;
import com.chalmers.irc2you.model.XMLHandler;

/**
 * The connection that the client uses to connect to the server with.
 * 
 * @author printz
 * 
 */
public class Connection {

	private static Connection INSTANCE;
	private static SharedPreferences settings;
	private static SharedPreferences.Editor settingsEditor;

	private static Context context;
	private static final String TAG = "irc2youConnection";

	private static BufferedReader in;
	private static PrintWriter out;
	private static Socket socket;
	private static String serverIp = null;
	private static String port;
	private static boolean connected = false;

	// Private constructor
	private Connection(Context context) {
		settings = PreferenceManager.getDefaultSharedPreferences(context);
		settingsEditor = settings.edit();
		Connection.serverIp = settings.getString("ip", "");
		Connection.port = settings.getString("port", "");
		Connection.context = context;
	}

	/**
	 * As Connection is a singleton, the only way of initializing it is through
	 * this method.
	 * 
	 * @return returns the only instance of the Connection
	 */
	public static Connection getInstance(Context context) {
		if (INSTANCE == null) {
			INSTANCE = new Connection(context);
		}
		return INSTANCE;
	}

	/**
	 * Posts a message in a channel on the irc-server.
	 * 
	 * @param channel
	 *            the name of the channel the message is to be sent to.
	 * @param msg
	 *            the message to be sent.
	 */
	public static void sendMessage(final String channel, final String msg) {
		try {
			socket = new Socket(serverIp, Integer.parseInt(port));
			out = new PrintWriter(socket.getOutputStream());
			in = new BufferedReader(new InputStreamReader(socket
					.getInputStream()));
			Log
					.i(TAG,
							"Opened socket and bound streams. Trying to send message.");

			send("<send><auth>" + auth() + "</auth><user>"
					+ settings.getString("user", "") + "</user><channel>"
					+ channel + "</channel><message>" + msg
					+ "</message></send>");
			out.close();
			in.close();
			socket.close();
		} catch (UnknownHostException e) {
			Log.e(TAG, "Don't know about host", e);
			couldNotConnect();
		} catch (IOException e) {
			Log.e(TAG, "Couldn't get the I/O for the socket", e);
			couldNotConnect();
		}

	}

	private static void couldNotAuth() {
		Toast toast = Toast
				.makeText(
						context,
						"Could not authenticate. Have you authenticated with the server?",
						Toast.LENGTH_LONG);
		toast.show();
	}

	private static void couldNotConnect() {
		Toast toast = Toast.makeText(context,
				"Could not connect to the server", Toast.LENGTH_LONG);
		toast.show();
	}

	/**
	 * Fetches the hilights from the server.
	 * 
	 * @return Returns all the hilights that has occured since the last time
	 *         that we fetched them. Returned as a LinkedList with Message as
	 *         elements
	 */
	public static LinkedList<Message> getHilights() {
		Log.i(TAG, "Connection started fetching hilights");
		LinkedList<Message> hilights = new LinkedList<Message>();
		try {
			socket = new Socket(serverIp, Integer.parseInt(port));
			out = new PrintWriter(socket.getOutputStream());
			in = new BufferedReader(new InputStreamReader(socket
					.getInputStream()));
			Log
					.i(TAG,
							"Opened socket and bound streams. Trying to send Hilight-request.");

			send("<hilight><auth>" + auth() + "</auth></hilight>");
			Log.i(TAG, "Sent the hilightrequest to the server");
			Log.i("irc2you connection", "Trying to read hilights from socket");
			try {
				SAXParserFactory spf = SAXParserFactory.newInstance();
				SAXParser sp = spf.newSAXParser();

				XMLReader xr = sp.getXMLReader();

				XMLHandler xmlHandler = new XMLHandler();
				xr.setContentHandler(xmlHandler);

				xr.parse(new InputSource(in));

				hilights = xmlHandler.getHilights();

			} catch (ParserConfigurationException pce) {
				Log.e("SAX XML", "sax parse error", pce);
			} catch (SAXException se) {
				Log.e("SAX XML", "sax error", se);
			} catch (IOException ioe) {
				Log.e("SAX XML", "sax parse io error", ioe);
			}
			out.close();
			in.close();
			socket.close();
		} catch (UnknownHostException e) {
			Log.e(TAG, "Don't know about host", e);
			couldNotConnect();
		} catch (IOException e) {
			Log.e(TAG, "Couldn't get I/O for the connection", e);
			couldNotConnect();
		}

		return hilights;
	}

	/**
	 * The method for fetching a channel from the server.
	 * 
	 * @param channel
	 *            the name of the channel to be fetched. Needs to correspond
	 *            with the name of the channel on the server and needs to be
	 *            defined in the config-file on the server
	 * @return A hashmap with the name of the channel at key: "name" and all the
	 *         rows in the channel as string-arrays with the key ""row" + i".
	 *         For an example of parsing this look at
	 *         DataHandler.createChannel(Hashmap<String>, <String[]>)
	 */
	public static Channel getChannelUpdates(String channel) {
		Log.i(TAG, "Started getting this channel from server: " + channel);
		Channel chan = new Channel("", null);
		SharedPreferences prefs = settings;

		try {
			socket = new Socket(serverIp, Integer.parseInt(port));
			out = new PrintWriter(socket.getOutputStream());
			in = new BufferedReader(new InputStreamReader(socket
					.getInputStream()));
			Log
					.i(TAG,
							"Openend socket and bound streams. Trying to send channel-request...");
			send("<getChannel><auth>" + auth() + "</auth><user>"
					+ settings.getString("user", "") + "</user><channel>"
					+ channel + "</channel></getChannel>");
			Log.i(TAG, "Sent the request for " + channel + "to server");
			Log.i(TAG, "Trying to read channel form socket");
			XMLHandler xmlHandler = new XMLHandler();

			try {
				SAXParserFactory spf = SAXParserFactory.newInstance();
				SAXParser sp = spf.newSAXParser();

				XMLReader xr = sp.getXMLReader();

				xr.setContentHandler(xmlHandler);

				// String xml = in.readLine();

				String xml = "<channel><name>#test</name><rows></rows></channel>";

				BufferedReader br = new BufferedReader(new StringReader(xml));
				InputSource is = new InputSource(br);

				xr.parse(is);

			} catch (ParserConfigurationException pce) {
				Log.e("SAX XML", "sax parse error", pce);
			} catch (SAXException se) {
				Log.e("SAX XML", "sax error", se);
			} catch (IOException ioe) {
				Log.e("SAX XML", "sax parse io error", ioe);
			}
			chan = xmlHandler.getChannel();
			out.close();
			in.close();
			socket.close();
		} catch (UnknownHostException e) {
			Log.e(TAG, "Don't know about host", e);
			couldNotConnect();
		} catch (IOException e) {
			Log.e(TAG, "Couldn't get the I/O for the connection");
			couldNotConnect();
		}
		return chan;
	}

	public static LinkedList<String> getChannels() {
		Log.i(TAG, "Started getting channels from server.");
		LinkedList<String> channels = new LinkedList<String>();

		try {
			socket = new Socket(serverIp, Integer.parseInt(port));
			out = new PrintWriter(socket.getOutputStream());
			in = new BufferedReader(new InputStreamReader(socket
					.getInputStream()));
			Log
					.i(TAG,
							"Openend socket and bound streams. Trying to send channel-request...");
			// TODO check protocol
			send("<getchannels><auth>" + auth() + "</auth><user>"
					+ settings.getString("user", "") + "</user></getchannels>");
			out.flush();
			Log.i(TAG, "Sent the request for the channels to server");
			Log.i(TAG, "Trying to read channel form socket");

			try {
				SAXParserFactory spf = SAXParserFactory.newInstance();
				SAXParser sp = spf.newSAXParser();

				XMLReader xr = sp.getXMLReader();

				XMLHandler xmlHandler = new XMLHandler();
				xr.setContentHandler(xmlHandler);

				xr.parse(new InputSource(in));

				channels = xmlHandler.getChannels();

			} catch (ParserConfigurationException pce) {
				Log.e("SAX XML", "sax parse error", pce);
			} catch (SAXException se) {
				Log.e("SAX XML", "sax error", se);
			}/*
			 * catch (IOException ioe) { Log.e("SAX XML", "sax parse io error",
			 * ioe); }
			 */
			out.close();
			in.close();
			socket.close();
		} catch (UnknownHostException e) {
			Log.e(TAG, "Don't know about host", e);
			couldNotConnect();
		} catch (IOException e) {
			Log.e(TAG, "Couldn't get the I/O for the connection");
			couldNotConnect();
		}
		return channels;
	}

	/**
	 * A synchronized method for sending requests to the server. Send anything
	 * through here so that we are sure to get the information we want in the
	 * right method when reading from the socket.
	 * 
	 * @param msg
	 *            the request in it's xml-format
	 */
	private static synchronized void send(String msg) {
		Log.i(TAG, "Sending message to server: " + msg);
		out.print(msg);
		out.flush();
	}

	private static synchronized String auth() {
		String shanumber = "";
		try {
			send("<auth><user>" + settings.getString("user", "")
					+ "</user></auth>");
			String authAns = in.readLine();
			String randNumber = authAns.split(">")[1].split("<")[0];
			Sha1 sha = new Sha1();
			String hashString = settings.getString("phoneId", "") + randNumber;
			Log.d(TAG, "Check hashString: " + hashString);
			shanumber = sha.SHA1(hashString);
			Log.d(TAG, "Check shanumber: " + shanumber);
		} catch (NoSuchAlgorithmException e) {
			Log.e(TAG, "There was no such Algorithm", e);
			couldNotAuth();
		} catch (IOException e) {
			Log.e(TAG, "Could not auth because of I/O", e);
			couldNotAuth();
		}
		return shanumber;
	}

	public static void connect() {
		try {
			socket = new Socket(serverIp, Integer.parseInt(port));
			out = new PrintWriter(socket.getOutputStream(), true);

			in = new BufferedReader(new InputStreamReader(socket
					.getInputStream()));
			Log.i(TAG, "Opened socket, bound streams.");
			out.println("<connect><user>" + settings.getString("user", "")
					+ "</user><nick>" + settings.getString("nick", "")
					+ "</nick></connect>");
			String phoneId = in.readLine();
			phoneId = phoneId.substring(phoneId.indexOf(">") + 1, phoneId
					.lastIndexOf("<"));

			settingsEditor.putString("phoneId", phoneId);
			settingsEditor.putBoolean("authenticated", true);
			settingsEditor.commit();
			out.close();
			in.close();
			socket.close();
		} catch (UnknownHostException e) {
			Log.e(TAG, "Don't know about host", e);
			couldNotConnect();
		} catch (IOException e) {
			Log.e(TAG, "Couldn't get the I/O for the socket", e);
			couldNotConnect();
		}
	}
}
